package cbr;

import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;

import base.WargusGameState;
import base.WargusPlayer;
import base.WargusStateImporter;
import base.WargusUnit;

public class CBRAdaptationUnits {

	public static PrintStream m_logger = null;
	
	/*
	 * Find the most similar self unit ID:
	 * The unit must satisfy that:
	 * 	- it can move if "move = true"
	 *  - it can attack if "attack = true"
	 *  - it can train the "train" unit if "train != null"
	 *  - it can build if "build = true"
	 */
	public static List<Integer> findMostSimilarSelfUnitID(WargusUnit originalUnit,WargusGameState a_gamestate, boolean move, boolean attack, String train,boolean build) throws Exception {
		List<WargusUnit> lu = findMostSimilarSelfUnit(originalUnit,a_gamestate,move,attack,train,build);
		List<Integer> l_ids = new LinkedList<Integer>();
		
		for(WargusUnit u:lu) l_ids.add(u.getUnitID());
		return l_ids;
	}


	public static List<WargusUnit> findMostSimilarSelfUnit (WargusUnit originalUnit,WargusGameState a_gamestate, boolean move, boolean attack, String train,boolean build) throws Exception {
		
		// find the most similar unit
		int MAX_UNITS_TO_RETURN = 3;
		List<WargusUnit> best_units = new LinkedList<WargusUnit>();
		List<Float> best_units_distance = new LinkedList<Float>();
		float d;

		if (originalUnit == null) throw new Exception("findMostSimilarSelfUnitID: unitID not found");

		m_logger.println("CBR: findMostSimilarSelfUnitID, original unit = " + originalUnit.toBriefString());
		System.out.println("CBR: findMostSimilarSelfUnitID, original unit = " + originalUnit.toBriefString());
		
		for(WargusUnit PU:a_gamestate.getPlayer().getUnits()) {

			if (PU!=null &&
				(!move || WargusStateImporter.canMove(WargusStateImporter.unitTypeToString(PU.getType()))) &&
				(!attack || WargusStateImporter.canAttack(WargusStateImporter.unitTypeToString(PU.getType()))) &&
				(train==null || WargusStateImporter.canTrain(WargusStateImporter.unitTypeToString(PU.getType()),train)) &&
				(!build || PU.getType()==2 || PU.getType()==3)) {
				
				d = PU.distance(originalUnit,0.4f,0.3f,0.1f,0.1f,0.1f);
				m_logger.println("CBR: findMostSimilarSelfUnitID, unit = " + PU.toBriefString() + " has distance = " + d);
//				System.out.println("CBR: findMostSimilarSelfUnitID, unit = " + PU.toBriefString() + " has distance = " + d);
				
				// Insert it in the list:
				{
					int insertion_point=-1;
					int j;
					
					for(j=0;j<best_units.size() && insertion_point==-1;j++) {
						if (d<best_units_distance.get(j)) insertion_point=j;
					} // for
					
					if (insertion_point==-1) {
						best_units.add(PU);
						best_units_distance.add(d);
					} else {
						best_units.add(insertion_point, PU);
						best_units_distance.add(insertion_point, d);
					}
					
					while(best_units.size()>MAX_UNITS_TO_RETURN) {
						best_units.remove(MAX_UNITS_TO_RETURN);
						best_units_distance.remove(MAX_UNITS_TO_RETURN);
					} // while					
				}				
			} else {
				if (PU!=null) {
					m_logger.println("CBR: findMostSimilarSelfUnitID discarded unit " + PU.toBriefString() + " since does not have the required capabilities...");
				}
			}
								
		}
		
		for(WargusUnit u:best_units) {
			m_logger.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
			System.out.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
		} // for

		return best_units;		
	}
	

	/*
	 * Find the most similar unit ID:
	 * The unit must satisfy that:
	 * 	- it can move if "move = true"
	 *  - it can attack if "attack = true"
	 *  - it can train the "train" unit if "train != null"
	 *  - it can build if "build = true"
	 */	
	
	public static List<Integer> findMostSimilarUnitID(WargusUnit originalUnit,WargusGameState a_gamestate, boolean move, boolean attack, String train,boolean build) throws Exception {		
		List<WargusUnit> lu = findMostSimilarUnit(originalUnit,a_gamestate,move,attack,train,build);
		List<Integer> l_ids = new LinkedList<Integer>();
		
		for(WargusUnit u:lu) l_ids.add(u.getUnitID());
		return l_ids;		
	}	
	public static List<WargusUnit> findMostSimilarUnit(WargusUnit originalUnit,WargusGameState a_gamestate, boolean move, boolean attack, String train,boolean build) throws Exception {
		
		// find the most similar unit
		int MAX_UNITS_TO_RETURN = 3;
		List<WargusUnit> best_units = new LinkedList<WargusUnit>();
		List<Float> best_units_distance = new LinkedList<Float>();
		float d;
			
		if (originalUnit == null) throw new Exception("findMostSimilarUnitID: unitID not found");
						
		m_logger.println("CBR: findMostSimilarUnitID, original unit = " + originalUnit.toBriefString());
		System.out.println("CBR: findMostSimilarUnitID, original unit = " + originalUnit.toBriefString());
		
		for(WargusPlayer p:a_gamestate.getPlayers()) {
			for(WargusUnit PU:p.getUnits()) {
				
//				System.out.println("Comparing with unit:\n " + CBLILRModule.GenerateXML(unit));			
				
				if (PU!=null &&
						(!move || WargusStateImporter.canMove(WargusStateImporter.unitTypeToString(PU.getType()))) &&
						(!attack || WargusStateImporter.canAttack(WargusStateImporter.unitTypeToString(PU.getType()))) &&
						(train==null || WargusStateImporter.canTrain(WargusStateImporter.unitTypeToString(PU.getType()),train)) &&
						(!build || PU.getType()==2 || PU.getType()==3)) {
					d = PU.distance(originalUnit,0.4f,0.3f,0.1f,0.1f,0.1f);
					m_logger.println("CBR: findMostSimilarUnitID, unit = " + PU.toBriefString() + " has distance = " + d);
//					System.out.println("CBR: findMostSimilarUnitID, unit = " + PU.toBriefString() + " has distance = " + d);

					// Insert it in the list:
					{
						int insertion_point=-1;
						int k;
						
						for(k=0;k<best_units.size() && insertion_point==-1;k++) {
							if (d<best_units_distance.get(k)) insertion_point=k;
						} // for
						
						if (insertion_point==-1) {
							best_units.add(PU);
							best_units_distance.add(d);
						} else {
							best_units.add(insertion_point, PU);
							best_units_distance.add(insertion_point, d);
						}
						
						while(best_units.size()>MAX_UNITS_TO_RETURN) {
							best_units.remove(MAX_UNITS_TO_RETURN);
							best_units_distance.remove(MAX_UNITS_TO_RETURN);
						} // while					
					}						
					
				} else {
					if (PU!=null) {
						m_logger.println("CBR: findMostSimilarSelfUnitID discarded unit " + PU.toBriefString() + " since does not have the required capabilities: " + move + " " + attack + " " + train);
					}
				}
									
			}
		
		}
		
		for(WargusUnit u:best_units) {
			m_logger.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
			System.out.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
		} // for

		return best_units;		
	}

	
	/*
	 * Find the most similar unit ID for target (the restriction is that the target must be reachable):
	 */	
	
	public static List<Integer> findMostSimilarTargetUnitID(WargusUnit originalUnit,WargusGameState a_gamestate,boolean selfAttack,int m_selfPlayerID,int startx,int starty,int range) throws Exception {	
		List<WargusUnit> lu = findMostSimilarTargetUnit(originalUnit,a_gamestate,selfAttack,m_selfPlayerID,startx,starty,range);
		List<Integer> l_ids = new LinkedList<Integer>();
		
		for(WargusUnit u:lu) l_ids.add(u.getUnitID());
		return l_ids;			
	}	
	public static List<WargusUnit> findMostSimilarTargetUnit(WargusUnit originalUnit,WargusGameState a_gamestate,boolean selfAttack,int m_selfPlayerID,int startx,int starty,int range) throws Exception {
		
		// find the most similar unit
		int MAX_UNITS_TO_RETURN = 3;
		List<WargusUnit> best_units = new LinkedList<WargusUnit>();
		List<Float> best_units_distance = new LinkedList<Float>();
		float d;
			
		if (originalUnit == null) throw new Exception("findMostSimilarTargetUnit: unitID not found");
				
		m_logger.println("");
		m_logger.println("CBR: findMostSimilarTargetUnit, original unit = " + originalUnit.toBriefString());
		System.out.println("CBR: findMostSimilarTargetUnit, original unit = " + originalUnit.toBriefString());
		
		for(WargusPlayer p:a_gamestate.getPlayers()) {
			for(WargusUnit PU:p.getUnits()) {
				
//				System.out.println("Comparing with unit:\n " + CBLILRModule.GenerateXML(unit));			
				
				if (PU!=null &&
					((selfAttack && PU.getPlayerID()==m_selfPlayerID) ||
					 (!selfAttack && PU.getPlayerID()!=m_selfPlayerID))) {
					if (inRange(PU,startx,starty,range,a_gamestate)) {
						d = PU.distance(originalUnit,0.4f,0.3f,0.1f,0.1f,0.1f);
						m_logger.println("CBR: findMostSimilarTargetUnit, unit = " + PU.toBriefString() + " has distance = " + d);
	//					System.out.println("CBR: findMostSimilarTargetUnit, unit = " + PU.toBriefString() + " has distance = " + d);

						// Insert it in the list:
						{
							int insertion_point=-1;
							int k;
							
							for(k=0;k<best_units.size() && insertion_point==-1;k++) {
								if (d<best_units_distance.get(k)) insertion_point=k;
							} // for
							
							if (insertion_point==-1) {
								best_units.add(PU);
								best_units_distance.add(d);
							} else {
								best_units.add(insertion_point, PU);
								best_units_distance.add(insertion_point, d);
							}
							
							while(best_units.size()>MAX_UNITS_TO_RETURN) {
								best_units.remove(MAX_UNITS_TO_RETURN);
								best_units_distance.remove(MAX_UNITS_TO_RETURN);
							} // while					
						}		
					} else {
						m_logger.println("CBR: findMostSimilarTargetUnit discarded unit " + PU.toBriefString() + " since it's not in range of: " + startx + "," + starty + " range " + range);						
					}
				}
			}		
		}
		
		for(WargusUnit u:best_units) {
			m_logger.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
			System.out.println("CBR: findMostSimilarSelfUnitID, selected = " + u.toBriefString());
		} // for

		return best_units;		
	}
	
	
	static boolean inRange(WargusUnit target,int start_x,int start_y,int range,WargusGameState a_gamestate) throws Exception {

		int i,j;
		int cycles = 0;
		int current;
		char t;
		LinkedList<Integer> open = new LinkedList<Integer>();
		int map_size = a_gamestate.getMap().get_height()*a_gamestate.getMap().get_width();
		boolean []closed = new boolean[map_size];
		int best_loc = -1;
		double best_distance = -1;

		for(i=0;i<map_size;i++) closed[i]=false;
		open.add(new Integer(start_x+start_y*a_gamestate.getMap().get_width()));
		while(open.size()>0) {
			cycles++;
			current = open.remove();
			
			if (!closed[current]) {
				
				// Consider the point:
				j = current%a_gamestate.getMap().get_width();
				i = current/a_gamestate.getMap().get_width();

				{
					double d = Math.sqrt((j-target.getLocX())*(j-target.getLocX()) + (i-target.getLocY())*(i-target.getLocY())) - 0.5; // substracting 0.5 to correct the rounding that stratagus does
					if (best_loc==-1 || d<best_distance) {
						best_loc = current;
						best_distance = d;
					}
					if (d<range) return true;					
				}
				
				if (j<a_gamestate.getMap().get_width()-1) {
					t = a_gamestate.getMap().get_map(j+1, i);
					if (!closed[current+1] &&
						(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS || 
						 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP)) {
						if (!open.contains(current+1)) open.add(current+1);
					}
				}
				if (j>0) {
					t = a_gamestate.getMap().get_map(j-1, i);
					if (!closed[current-1] &&
						(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS || 
						 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP)) {
						if (!open.contains(current-11)) open.add(current-1);
					}
				}
				if (i<a_gamestate.getMap().get_height()-1) {
					t = a_gamestate.getMap().get_map(j, i+1);
					if (!closed[current+a_gamestate.getMap().get_width()] &&
						(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS || 
						 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP)) {
						if (!open.contains(current+a_gamestate.getMap().get_width())) open.add(current+a_gamestate.getMap().get_width());
					}
				}
				if (i>0) {
					t = a_gamestate.getMap().get_map(j, i-1);
					if (!closed[current-a_gamestate.getMap().get_width()] &&
						(t == WargusStateImporter.TERRAIN_NONE || t == WargusStateImporter.TERRAIN_GRASS || 
						 t == WargusStateImporter.TERRAIN_UNIT || t == WargusStateImporter.TERRAIN_UNITOPP)) {
						if (!open.contains(current-a_gamestate.getMap().get_width())) open.add(current-a_gamestate.getMap().get_width());
					}
				}					
				
				closed[current]=true;
			}
		}
		
		// Show parts of the map considered:
		m_logger.println("Portion of the map considered: (in " + cycles + " cycles)");
		m_logger.println("Best coordinates found: " + (best_loc%a_gamestate.getMap().get_width()) + "," +
													  (best_loc/a_gamestate.getMap().get_width()) + " distance " + best_distance);
		for(i=0;i<map_size;i++) {
			if ((target.getLocX()+target.getLocY()*a_gamestate.getMap().get_width())==i) {
				m_logger.print("X");
			} else if ((start_x+start_y*a_gamestate.getMap().get_width())==i) {
				m_logger.print("O");
			} else {
				if (closed[i]) m_logger.print("*");
						  else m_logger.print(".");
			}
			if (((i+1)%a_gamestate.getMap().get_width())==0) m_logger.println("");
		}		
				
		return false;
	}

	
}
